﻿using CG5_KT.Primitives;
using CG5_KT.Scene;
using CG5_KT.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace CG5_KT.Forms
{
    public partial class MainForm : Form
    {
        private bool appLoopCompleted;
        private bool appClosePending;

        private BackgroundWorker appLoopBW;
        private FPSCalc fpsCalc;

        private List<Model> models;
        private Model terrain;
        private Model airCraft;
        //private Model helpNoseCam;
        private Route route;

        private Engine engine;
        private Camera camera;

        public MainForm()
        {
            this.InitializeComponent();
            this.InitializeEngine();
            this.InitializeModels();
            this.InitializeRoute();
            this.InitializeAndStartAppLoop();
        }

        private void InitializeEngine()
        {
            this.fpsCalc = new FPSCalc();
            this.fpsCalc.FPSChanged += fpsCounter_FPSChanged;

            // Bitmap format is 32 bits per pixel; 8 bits each are used for the
            // alpha, red, green, and blue components.
            var drawingAreaBmp = new Bitmap(
                this.opaquePanelScene.Width,
                this.opaquePanelScene.Height,
                PixelFormat.Format32bppArgb);

            this.engine = new Engine(drawingAreaBmp);

            this.camera = new Camera();
            this.camera.Position = new Vector3(0, 0, 10);
            //this.camera.Position = new Vector3(0, 10, 10f);
            this.camera.Target = Vector3.Zero;
        }

        private void InitializeModels()
        {
            this.terrain = ModelFactory.ConstructTerrainAndBaloons();
            this.terrain.Translate(new Vector3(0, -0.5f, 0));

            this.airCraft = ModelFactory.ConstructAircraft();

            this.models = new List<Model>();
            this.models.Add(this.airCraft);
            this.models.Add(this.terrain);

            // TODO: remove later.
            /*this.helpNoseCam = new Model();
            this.helpNoseCam.Meshes.Add(MeshFactory.ConstructCube());
            this.helpNoseCam.Meshes[0].Scale(new Vector3(0.1f, 0.1f, 0.1f));
            this.models.Add(this.helpNoseCam);*/
        }

        private void InitializeRoute()
        {
            this.route = new Route();
            this.route.IsDrawCtrlPtsFlag = this.rbRouteShow.Checked;
            this.route.IsDrawPathFlag = this.rbRouteShow.Checked;
        }

        #region Application Loop

        private void InitializeAndStartAppLoop()
        {
            this.appLoopBW = new BackgroundWorker();
            this.appLoopBW.WorkerSupportsCancellation = true;
            this.appLoopBW.DoWork += new DoWorkEventHandler(this.AppLoopDoWork);
            this.appLoopBW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.AppLoopCompleted);
            this.appLoopBW.RunWorkerAsync();
        }

        private void AppLoopDoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            while(true)
            {
                if ((worker.CancellationPending == true))
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    this.opaquePanelScene.Invalidate();
                }
            }
        }

        private void AppLoopCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.appLoopCompleted = true;

            if (this.appClosePending == true)
            {
                this.Close();
            }
        }

        #endregion

        #region Event Handlers

        private void fpsCounter_FPSChanged(int newFPS)
        {
            this.Text = String.Format("FPS: {0}", newFPS);
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (this.appLoopCompleted == false)
            {
                // Request application loop to break.
                this.appLoopBW.CancelAsync();

                // Disable main form's user interaction.
                this.Enabled = false;

                // Cancel current closing event (to be invoked again by worker thread).
                e.Cancel = true;

                // Indicate that user requested to close application.
                this.appClosePending = true;

                return;
            }
        }

        private void opaquePanelScene_Paint(object sender, PaintEventArgs e)
        {
            this.RepaintScene(e.Graphics);
        }

        private void opaquePanelScene_Resize(object sender, EventArgs e)
        {
            if (this.opaquePanelScene.Width == 0 || this.opaquePanelScene.Height == 0)
            {
                // e.g. Window was minimized, do nothing.
                return;
            }

            // Bitmap format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.
            var drawingAreaBmp = new Bitmap(
                this.opaquePanelScene.Width,
                this.opaquePanelScene.Height,
                PixelFormat.Format32bppArgb);
            
            this.engine = new Engine(drawingAreaBmp);
        }

        private void opaquePanelScene_KeyDown(object sender, KeyEventArgs e)
        {
            this.camera.FollowedObjectPosition = this.airCraft.Position;

            switch (e.KeyCode)
            {
                case Keys.W:
                    this.camera.GoUp();
                    break;
                case Keys.S:
                    this.camera.GoDown();
                    break;
                case Keys.A:
                    this.camera.GoLeft();
                    break;
                case Keys.D:
                    this.camera.GoRight();
                    break;
                case Keys.R:
                    this.camera.GoForward();
                    break;
                case Keys.F:
                    this.camera.GoBackward();
                    break;
            }
        }

        private void rbCameraFree_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.camera.Mode = Camera.CameraMode.Free;
            this.camera.Position = new Vector3(0, 0, 10.0f);
            this.camera.Target = Vector3.Zero;

            // Set focus on panel to let it further generate key pressed events.
            this.OnRadioClickSetFocusOnOpaquePanel(sender, e);
        }

        private void rbCameraTPP_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.camera.Mode = Camera.CameraMode.TPP;
            this.camera.Target = this.airCraft.Position;
            this.camera.FollowedObjectPosition = this.airCraft.Position;
            this.camera.Position = new Vector3(
                this.airCraft.Position.X,
                this.airCraft.Position.Y,
                this.airCraft.Position.Z + 5);

            // Set focus on panel to let it further generate key pressed events.
            this.OnRadioClickSetFocusOnOpaquePanel(sender, e);
        }

        private void rbCameraNose_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.camera.Mode = Camera.CameraMode.Nose;
            /*this.camera.Position = new Vector3(
                this.airCraft.Position.X,
                this.airCraft.Position.Y,
                this.airCraft.Position.Z - 5);
            this.camera.Target = this.airCraft.Position;*/

            // Set focus on panel to let it further generate key pressed events.
            this.OnRadioClickSetFocusOnOpaquePanel(sender, e);
        }

        private void rbRouteShow_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.route.IsDrawCtrlPtsFlag = true;
            this.route.IsDrawPathFlag = true;

            // Set focus on panel to let it further generate key pressed events.
            this.OnRadioClickSetFocusOnOpaquePanel(sender, e);
        }

        private void rbRouteHide_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.route.IsDrawCtrlPtsFlag = false;
            this.route.IsDrawPathFlag = false;

            // Set focus on panel to let it further generate key pressed events.
            this.OnRadioClickSetFocusOnOpaquePanel(sender, e);
        }

        private void rbShadingFlat_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.engine.Shading = Engine.ShadingMode.Flat;
        }

        private void rbShadingGouraud_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.engine.Shading = Engine.ShadingMode.Gouraud;
        }

        private void rbShadingPhong_CheckedChanged(object sender, EventArgs e)
        {
            if ((sender as RadioButton).Checked == false)
            {
                return;
            }

            this.engine.Shading = Engine.ShadingMode.Phong;
        }

        private void OnRadioClickSetFocusOnOpaquePanel(object sender, EventArgs e)
        {
            // Set focus on panel to make it generate key pressed events.
            // In case focus is not on panel (but on e.g. radio) then panel does
            // not generate W,S,A,D,R,F key pressed events and camera does not move.
            this.opaquePanelScene.Focus();
        }
        #endregion

        private void RepaintScene(Graphics gr)
        {
            this.fpsCalc.CalculateFrameRate();

            this.engine.Clear(0, 0, 255, 255);

            // foreach (var mesh in this.meshes)
            // {
                // Rotating slightly the meshes during each frame rendered.
                // mesh.Rotation = new Vector3(mesh.Rotation.X + 0.0005f, mesh.Rotation.Y + 0.0005f, mesh.Rotation.Z +0.00f);
            // }

            this.ProcessAircraftMovementAndCamera();

            this.engine.Render(this.camera, this.models, this.route);
            this.engine.Present(gr);
        }

        private void ProcessAircraftMovementAndCamera()
        {
            this.route.NextSegSplitStep();

            // Aircraft rotation.
            Vector3 airCraftRotation = this.route.CalcAircraftYawAndPitch();
            this.airCraft.Rotate(airCraftRotation);

            // Aircraft translation.
            Vector3 airCraftPrevPos = this.airCraft.Position;
            Vector3 airCraftNewPos = this.route.CalcAircraftPos();
            Vector3 airCraftTranslation = airCraftNewPos - airCraftPrevPos;

            this.airCraft.Translate(airCraftTranslation);

            if (this.camera.Mode == Camera.CameraMode.TPP)
            {
                this.camera.FollowedObjectPosition = this.airCraft.Position;
                this.camera.Strafe(airCraftTranslation);
            }
            else if (this.camera.Mode == Camera.CameraMode.Nose)
            {
                Vector3 airCraftMove = airCraftNewPos - airCraftPrevPos;
                airCraftMove.Normalize();

                // this.helpNoseCam.Meshes[0].Position = this.airCraft.Position - airCraftMove * 10;

                this.camera.Position = this.airCraft.Position - airCraftMove * 5f;
                this.camera.Target = this.airCraft.Position - airCraftMove * 10;
            }
        }
    }
}
